/*
 * Some features based on ping.c 
 * Author -
 *    Mike Muuss
 *    U. S. Army Ballistic Research Laboratory
 *    December, 1983
 * Modified at Uc Berkeley
 *
 * Phoebus Ping version by Ezra Kissel
 * May 2008
 */

#include "config.h"

#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <netdb.h>
#include <sys/time.h>

#include "xsp-proto.h"

#include "libxsp_client.h"

#define MAXWAIT 10

void finish(), catcher(), pinger();
char **split(const char *string, char *delimiters, int *count);

libxspSess *sess;
struct timeval stime, etime;

char *host;
int connfd = -1;

int npackets = 4;
int nreceived = 0;
int ntransmitted = 0;
int timing = 0;
double tmin = 999999999;
double tmax = 0;
double tsum = 0;

int tcpopen(char *host, int port)
{
	int     unit;
	struct  sockaddr_in     sin;
	struct  hostent         *hp;

	hp = gethostbyname(host);
	if (hp == 0) {
		herror("Cannot find host");
		exit(h_errno);
	}

	bzero((char*)&sin, sizeof(struct sockaddr_in));

	sin.sin_family = AF_INET;

	memcpy(&(sin.sin_addr), hp->h_addr, hp->h_length);
	sin.sin_port = htons(port);

	if ((unit = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
		{
			perror("Could not open socket");
			exit(errno);
		}

	if ( connect(unit, (const struct sockaddr*)&sin,
		     sizeof(struct sockaddr_in)) < 0)
		{
			perror("Could not connect to socket");
			exit(errno);
		}
	return(unit);
}

int main(int argc, char *argv[])
{
	extern char *optarg;
	extern int errno;
	extern int optind;
	int c;
	int debug = 0;
	static char usage[] = "usage: phoebus_ping [-V] host/port\n";
	double ttime;
	char **conn_str;
	int count;

	signal(SIGINT, finish);
	signal(SIGALRM, catcher);

	while((c = getopt(argc, argv, "t:d:i:t:V")) != -1) {
		switch(c) {
		case 'V':
			printf("Phoebus Gateway Pinger, version 0.1\n");
			printf("%s\n", usage);
			exit(1);
			break;
			
		case 't':
			npackets = atoi(optarg);
			break;
			
		case 'd':
			debug = atoi(optarg);
			if (debug < 1) debug = 1;
			break;
			
		default:
			fprintf(stderr, usage); 
			exit(1);
		}
	}
	
	if (libxsp_init() < 0) {
		perror("libxsp_init(): failed");
		exit(errno);
	}

	sess = xsp_session();
	if (!sess) {
		perror("xsp_session() failed");
		exit(errno);
	}

	if(optind == argc) {
		fprintf(stderr, usage);
		exit(0);
	}

	host = argv[argc - 1];
	
	conn_str = split(host, "/", &count);
	connfd = tcpopen(conn_str[0], atoi(conn_str[1]));

	xsp_set_session_connected(sess);
	xsp_set_session_socket(sess, connfd);
	
	catcher();

	for (;;) {
		xsp_recv_ping(sess);
		gettimeofday(&etime, NULL);
		
		stime.tv_usec += (stime.tv_sec * 1000000);
		etime.tv_usec += (etime.tv_sec * 1000000);
		
		ttime = (etime.tv_usec -= stime.tv_usec);
		ttime = ttime / 1000;

		tsum += ttime;
		if (ttime < tmin)
			tmin = ttime;
		if (ttime > tmax)
			tmax = ttime;
		
		printf("PING %s time=%.3f ms\n", host, ttime);
		nreceived++;
	}

	xsp_close2(sess);
	
	return 0;
}

void catcher() {
	int waittime;
	
	pinger();
	if (npackets == 0 || ntransmitted < npackets)
		alarm(1);
	else {
		if (nreceived) {
			waittime = 2 * tmax / 1000;
			if (waittime == 0)
				waittime = 1;
		}
		else
			waittime = MAXWAIT;
		signal(SIGALRM, finish);
		alarm(waittime);
	}
}

void pinger() {
	// could add seq nums to PING/PONG messages
	// to help with proper timings
	
	gettimeofday(&stime, NULL);
	xsp_send_ping(sess);
	ntransmitted++;
}

void finish() {
	putchar('\n');
	fflush(stdout);
	printf("\n----%s PING Statistics----\n", host );
	printf("%d packets transmitted, ", ntransmitted );
	printf("%d packets received, ", nreceived );
	if (ntransmitted)
		if( nreceived > ntransmitted)
			printf("-- somebody's printing up packets!");
		else
			printf("%d%% packet loss", 
			       (int) (((ntransmitted-nreceived)*100) /
				      ntransmitted));
	printf("\n");
	printf("round-trip (ms)  min/avg/max = %.3f/%.3f/%.3f\n",
	       tmin,
	       tsum / nreceived,
	       tmax );
	fflush(stdout);

	xsp_close2(sess);
	close(connfd);
	exit(0);
}
